package com.literaryfamily.literaryfamily.myUtil;

import com.literaryfamily.literaryfamily.config.EncryptionKey;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.text.Normalizer;
import java.util.Base64;

/**
 * 加密工具类，提供密码哈希生成和验证功能。
 * 使用PBKDF2WithHmacSHA256算法结合随机盐和固定盐进行密码加密，
 * 防止彩虹表攻击并增强安全性。
 */
@Component
public class PasswordEncryptionUtil {
    /**
     * 随机盐的长度（字节数）
     */
    private static final int SALT_LENGTH = 32;

    /**
     * PBKDF2算法的迭代次数
     */
    private static final int ITERATIONS = 114514;

    /**
     * 生成的哈希值的长度（bit）
     */
    private static final int KEY_LENGTH = 256;

    /**
     * 固定盐值
     */
    @Autowired
    private EncryptionKey encryptionProperties;


    /**
     * 生成随机盐值。
     *
     * @return 包含随机盐值的字节数组，长度为SALT_LENGTH
     */
    public static byte[] generateSalt() {
        SecureRandom random = new SecureRandom();
        byte[] salt = new byte[SALT_LENGTH];
        random.nextBytes(salt);
        return salt;
    }

    /**
     * 对密码进行加密处理。
     *
     * @param password 需要加密的密码字符串
     * @return 格式为"Base64(随机盐):Base64(哈希值)"的字符串，若输入密码为空则返回null
     */
    @Nullable
    public String hashPassword(String password) {
        if (StringUtil.isNull(password)) return null;
        password = Normalizer.normalize(password, Normalizer.Form.NFC); // 统一为 NFC 格式
        return hashPassword(password.toCharArray(), null);
    }

    /**
     * 使用PBKDF2算法对密码进行哈希处理。
     * <p>
     * 若未提供随机盐，则自动生成随机盐。
     *
     * @param password   要哈希的密码字符数组
     * @param randomSalt 随机盐值字节数组（可为null）
     * @return 格式为"Base64(随机盐):Base64(哈希值)"的字符串
     * @throws RuntimeException 当加密过程中发生错误时抛出
     */
    public String hashPassword(char[] password, @Nullable byte[] randomSalt) {
        try {
            if (randomSalt == null) {
                randomSalt = generateSalt();
            }
            PBEKeySpec spec = new PBEKeySpec(password, getSalt(randomSalt), ITERATIONS, KEY_LENGTH);
            SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
            byte[] hash = skf.generateSecret(spec).getEncoded();
            return Base64.getEncoder().encodeToString(randomSalt) + ":" + Base64.getEncoder().encodeToString(hash);
        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
            LogUtil.error(PasswordEncryptionUtil.class, "密码加密失败!");
            LogUtil.error(PasswordEncryptionUtil.class, e);
            throw new RuntimeException("密码加密失败", e);
        }
    }

    /**
     * 将固定盐和随机盐组合生成最终盐值。
     *
     * @param salt 随机盐值字节数组
     * @return 组合后的盐值字节数组（固定盐 + 随机盐）
     */
    private byte[] getSalt(byte[] salt) {
        byte[] fixedSalt = encryptionProperties.getKey().getBytes(StandardCharsets.UTF_8);
        byte[] combinedSalt = new byte[fixedSalt.length + salt.length];
        System.arraycopy(fixedSalt, 0, combinedSalt, 0, fixedSalt.length);
        System.arraycopy(salt, 0, combinedSalt, fixedSalt.length, salt.length);
        return combinedSalt;
    }

    /**
     * 验证输入的密码是否与存储的哈希值匹配。
     *
     * @param inputPassword 待验证的密码字符串
     * @param storedValue   存储的哈希值（格式为"Base64(随机盐):Base64(哈希值)"）
     * @return 若密码匹配返回true，输入为空或格式错误返回false
     */
    public boolean verifyPassword(String inputPassword, String storedValue) {
        if (StringUtil.isNull(inputPassword) || StringUtil.isNull(storedValue)) return false;
        inputPassword = Normalizer.normalize(inputPassword, Normalizer.Form.NFC);
        return verifyPassword(inputPassword.toCharArray(), storedValue);
    }

    /**
     * 验证输入的密码字符数组是否与存储的哈希值匹配。
     * <p>
     * 使用恒定时间比较防止时序攻击。
     *
     * @param inputPassword 待验证的密码字符数组
     * @param storedValue   存储的哈希值（格式为"Base64(随机盐):Base64(哈希值)"）
     * @return 若密码匹配返回true，格式错误或异常时返回false
     */
    private boolean verifyPassword(char[] inputPassword, String storedValue) {
        try {
            String[] parts = storedValue.split(":");
            if (parts.length != 2) return false;

            byte[] randomSalt = Base64.getDecoder().decode(parts[0]);
            byte[] storedHash = Base64.getDecoder().decode(parts[1]);

            PBEKeySpec spec = new PBEKeySpec(inputPassword, getSalt(randomSalt), ITERATIONS, KEY_LENGTH);
            SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
            byte[] inputHash = skf.generateSecret(spec).getEncoded();

            return MessageDigest.isEqual(storedHash, inputHash);
        } catch (Exception e) {
            LogUtil.error(PasswordEncryptionUtil.class, "密码验证失败!");
            LogUtil.error(PasswordEncryptionUtil.class, e);
            return false;
        }
    }


    /**
     * 使用 PBKDF2 算法从原始密钥派生出标准密钥（用于加密等场景）
     *
     * @param rawKey 原始密钥字符串
     * @return 派生后的密钥字节数组
     */
    public byte[] deriveKey(String rawKey) {
        try {
            byte[] salt = encryptionProperties.getKey().getBytes(StandardCharsets.UTF_8); // 固定盐
            PBEKeySpec spec = new PBEKeySpec(rawKey.toCharArray(), salt, ITERATIONS, KEY_LENGTH);
            SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
            return factory.generateSecret(spec).getEncoded();
        } catch (Exception e) {
            throw new RuntimeException("密钥派生失败", e);
        }
    }

}